﻿namespace Atomic.Images
{
    using System;
    using System.IO;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Drawing.Drawing2D;

    using Atomic.Extensions;
    using Atomic.Exceptions;

    /// <summary>
    /// 图片管理器。
    /// </summary>
    public class ImageManager
    {
        /// <summary>
        /// 图片管理器。
        /// </summary>
        private static readonly ImageManager _imageManager = new ImageManager();

        /// <summary>
        /// 当前图片管理器。
        /// </summary>
        public static ImageManager Current
        {
            get
            {
                return _imageManager;
            }
        }

        /// <summary>
        /// 上传并按比例缩放图片。
        /// </summary>
        /// <param name="uploadImage">图片上传信息。</param>
        /// <param name="newFileNameAction">生成新图片名称。</param>
        /// <param name="newFileName">带后缀新图片名称。</param>
        public ViewUploadImage LoadImage(UploadImage uploadImage, Func<string> newFileNameAction, out string newFileName)
        {
            if (string.IsNullOrWhiteSpace(uploadImage.FileName))
            {
                throw new ArgumentNullException("fileName", "文件名不可以是空的。");
            }

            string fileExt = Path.GetExtension(uploadImage.FileName);

            if (string.IsNullOrWhiteSpace(fileExt))
            {
                throw new ArgumentNullException("fileExt", "文件名不可以没有后缀。");
            }

            newFileName = newFileNameAction() + fileExt.ToLower();

            string filePath = Path.Combine(uploadImage.SavePath, newFileName);

            if (!Directory.Exists(uploadImage.SavePath))
            {
                Directory.CreateDirectory(uploadImage.SavePath);
            }

            ViewUploadImage view = new ViewUploadImage();

            view.FileName = this.GetFileName(filePath);
            view.FileExt = fileExt;

            Image sourceImage = Image.FromStream(uploadImage.Stream);

            var targetImage = this.GetThumbnailImage(sourceImage, uploadImage.Width, uploadImage.Height, true);

            var size = this.GetImageSize(new Size(sourceImage.Width, sourceImage.Height), new Size(uploadImage.Width, uploadImage.Height));

            view.Width = sourceImage.Width;
            view.Height = sourceImage.Height;
            view.ViewWidth = size.Width;
            view.ViewHeight = size.Height;

            this.SaveImage(targetImage, filePath, true);

            sourceImage.Dispose();
            targetImage.Dispose();

            return view;
        }

        /// <summary>
        /// 裁剪并缩放正方形图片。
        /// </summary>
        /// <remarks>
        /// 1.先在原图上根据 x,y 坐标开始裁剪，裁剪的宽和高就是width,height。
        /// 2.在将裁剪出来后的图片，按照传入 side 缩略图尺寸来将裁剪出来的图片进行缩小或者放大。
        /// 3.保存图片。
        /// </remarks>
        /// <param name="sourcePath">原图位置（包含文件名）。</param>
        /// <param name="savePath">保存位置（目录）。</param>
        /// <param name="side">裁剪后的正方型缩略图尺寸。</param>
        /// <param name="high">是否设置绘制高质量。</param>
        /// <param name="cutImage">裁剪图片信息。</param>
        /// <param name="fileName">文件名（不带后缀）。</param>
        /// <param name="sizes">缩略图尺寸。</param>
        public void CutImage(string sourcePath, string savePath, int side, bool high, CutImageModel cutImage, string fileName, params int[] sizes)
        {
            ////判断原文件是否存在。
            //if(!File.Exists(sourcePath))
            //{
            //    throw new IOException(string.Format("路径：“{0}”文件不存在。", sourcePath));
            //}

            if(cutImage == null)
            {
                throw new ArgumentNullException("cutImage", "cutImage 对象不可以是空的。");
            }

            if (cutImage.Width <= 0 || cutImage.Height <= 0)
            {
                throw new AtomicException("裁剪图片区域的宽度或者高度必须是大于 0 的。");
            }

            if (cutImage.AreaWidth <= 0 || cutImage.AreaHeight <= 0)
            {
                throw new AtomicException("原图预览区域的宽度或者高度必须是大于 0 的。");
            }

            using (Image sourceImage = Image.FromFile(sourcePath))
            {
                //计算并返回原图的裁剪区域范围矩形。
                var cutRectangle = this.GetCutRectangle(
                    sourceImage.Width, sourceImage.Height, cutImage.AreaWidth, cutImage.AreaHeight, new Rectangle(cutImage.X, cutImage.Y, cutImage.Width, cutImage.Height));

                //裁剪原图。
                var targetImage = this.CutImage(sourceImage, cutRectangle, high);

                //按 side 尺寸缩放裁剪后的图片。
                var resultImage = this.GetThumbnailImage(targetImage, side, side, high);

                //获得原图扩展名。
                string fileExt = Path.GetExtension(sourcePath);

                //组合保存图片的路径。
                string saveImagePath = savePath.EndsWith("\\") ? savePath + fileName + "_" + side + fileExt : savePath + "\\" + fileName + "_" + side + fileExt;

                //保存图片（只支付 jpg, gif, png）。
                SaveImage(resultImage, saveImagePath, high);

                //生成缩略图
                if (sizes != null && sizes.Length > 0)
                {
                    foreach (int size in sizes)
                    {
                        var thumbnailImage = this.GetThumbnailImage(resultImage, size, size, high);

                        if (thumbnailImage != null)
                        {
                            saveImagePath = savePath.EndsWith("\\") ? savePath + fileName + "_" + size + fileExt : savePath + "\\" + fileName + "_" + size + fileExt;
                            SaveImage(thumbnailImage, saveImagePath, high);
                            thumbnailImage.Dispose();
                        }
                    }
                }

                targetImage.Dispose();
                resultImage.Dispose();
            }

            ////删除原图片。
            //File.Delete(sourcePath);
        }

        /// <summary>
        /// 获得指定图片尺寸的缩放尺寸。
        /// </summary>
        /// <param name="imageSize">原图尺寸。</param>
        /// <param name="maxSize">最大尺寸。</param>
        /// <returns>缩放后的尺寸。</returns>
        private Size GetImageSize(Size imageSize, Size maxSize)
        {
            double w = 0.0;
            double h = 0.0;
            double sw = Convert.ToDouble(imageSize.Width);
            double sh = Convert.ToDouble(imageSize.Height);
            double mw = Convert.ToDouble(maxSize.Width);
            double mh = Convert.ToDouble(maxSize.Height);
            if (sw < mw && sh < mh)
            {
                w = sw;
                h = sh;
            }
            else if ((sw / sh) > (mw / mh))
            {
                w = maxSize.Width;
                h = (w * sh) / sw;
            }
            else
            {
                h = maxSize.Height;
                w = (h * sw) / sh;
            }

            int width = Convert.ToInt32(w);
            int height = Convert.ToInt32(h);

            return new Size(width, height);
        }

        /// <summary>
        /// 获得裁剪正方形区域矩形。
        /// </summary>
        /// <remarks>
        /// 根据 cutRectangle 与原图显示区域的比例来计算出原图的裁剪区域范围矩形位置与大小。
        /// </remarks>
        /// <param name="width">原图宽度。</param>
        /// <param name="height">原图高度。</param>
        /// <param name="areaWidth">原图显示区域宽度。</param>
        /// <param name="areaHieght">原图显示区域高度。</param>
        /// <param name="cutRectangle">原图显示区域的裁剪区域范围矩形。</param>
        /// <returns>原图的裁剪区域范围矩形。</returns>
        private Rectangle GetCutRectangle(double width, double height, double areaWidth, double areaHieght, Rectangle cutRectangle)
        {
            double w = 0.0;
            double h = 0.0;

            //计算出源图片缩小到显示区域范围内的宽度和高度。
            if (width < areaWidth && height < areaHieght)
            {
                //没超出，不做计算。
                w = width;
                h = height;
            }
            else if ((width / height) > (areaWidth / areaHieght))
            {
                w = areaWidth;
                h = (w * height) / width;
            }
            else
            {
                h = areaHieght;
                w = (h * width) / height;
            }

            //计算出显示区域的图片的宽度与裁剪图片的宽度的比例。
            double widthScale = w / cutRectangle.Width;

            //计算出显示区域的图片的高度与裁剪图片的高度的比例。
            double heightScale = h / cutRectangle.Height;

            //计算出 X 坐标的比例（公式：显示区域宽度 / X 坐标）
            double smallWidthScale = w / cutRectangle.X;

            //计算出 Y 坐标的比例（公式：显示区域高度 / Y 坐标）
            double smallHeightScale = h / cutRectangle.Y;

            Rectangle resultRectangle = new Rectangle
            {
                //裁剪源图片的 X 坐标是：源图片宽度 / X 坐标比例。
                X = (int)(width / smallWidthScale),
                //裁剪源图片的 Y 坐标是：源图片高度 / Y 坐标比例。
                Y = (int)(height / smallHeightScale),
                //裁剪源图片的宽度是：源图片宽度 / 宽度比例。
                Width = (int)(width / widthScale),
                //裁剪源图片的高度是：源图片高度 / 高度比例。
                Height = (int)(height / heightScale)
            };

            return resultRectangle;
        }

        /// <summary>
        /// 裁剪图片。
        /// </summary>
        /// <param name="sourceImage">原图对象。</param>
        /// <param name="cutRectangle">裁剪矩形信息。</param>
        /// <param name="high">是否设置绘制高质量。</param>
        /// <returns>裁剪后的图片对象。</returns>
        private Bitmap CutImage(Image sourceImage, Rectangle cutRectangle, bool high)
        {
            //根据传入的裁剪坐标与宽高来裁剪出图片。
            var targetImage = new Bitmap(cutRectangle.Width, cutRectangle.Height);
            Graphics graphics = Graphics.FromImage(targetImage);

            if (high)
            {
                graphics.CompositingQuality = CompositingQuality.HighQuality;
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
            }

            //裁剪图片的矩形。
            var toRectangle = new Rectangle(0, 0, cutRectangle.Width, cutRectangle.Height);
            //裁剪图片在原图的位置的矩形。
            var fromRectangle = new Rectangle(cutRectangle.X, cutRectangle.Y, cutRectangle.Width, cutRectangle.Height);
            //将要裁剪的区域绘制到 image 对象里，该对象表示的是裁剪后的图片。
            graphics.DrawImage(sourceImage, toRectangle, cutRectangle, GraphicsUnit.Pixel);

            graphics.Dispose();

            return targetImage;
        }

        /// <summary>
        /// 获得重新绘制的缩略图对象。
        /// </summary>
        /// <param name="image">原图对象。</param>
        /// <param name="maxWidth">缩略图最大宽度。</param>
        /// <param name="maxHeight">缩略图最大高度。</param>
        /// <param name="high">是否设置绘制高质量。</param>
        /// <returns>绘制缩略图对象。</returns>
        private Bitmap GetThumbnailImage(Image image, int maxWidth, int maxHeight, bool high)
        {
            //缩略图尺寸
            Size newSize = this.GetImageSize(new Size(image.Width, image.Height), new Size(maxWidth, maxHeight));

            //创建一张缩略图对象。
            var targetImage = new Bitmap(newSize.Width, newSize.Height);

            Graphics graphics = Graphics.FromImage(targetImage);

            if (high)
            {
                graphics.CompositingQuality = CompositingQuality.HighQuality;
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
            }

            //缩略图的矩形。
            var toRectangle = new Rectangle(0, 0, newSize.Width, newSize.Height);
            //缩略图在原图的位置的矩形。
            var fromRectangle = new Rectangle(0, 0, image.Width, image.Height);
            //将要原图绘制到 targetImage 对象里，该对象表示的是缩小后的图片。
            graphics.DrawImage(image, toRectangle, fromRectangle, GraphicsUnit.Pixel);

            graphics.Dispose();

            return targetImage;
        }

        /// <summary>
        /// 生成图片。
        /// </summary>
        /// <param name="image">要生成的图片对象。</param>
        /// <param name="savePath">保存位置。</param>
        /// <param name="high">是否设置绘制高质量。</param>
        private void SaveImage(Image image, string savePath, bool high)
        {
            string fileExt = Path.GetExtension(savePath);

            ImageFormat imageFormat = ImageFormat.Jpeg;

            if (fileExt.Equals(".gif", StringComparison.OrdinalIgnoreCase))
            {
                imageFormat = ImageFormat.Gif;
            }

            if (fileExt.Equals(".png", StringComparison.OrdinalIgnoreCase))
            {
                imageFormat = ImageFormat.Png;
            }

            var directory = Directory.GetParent(savePath).FullName;

            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            if (high)
            {
                //以下代码为保存图片时，设置压缩质量。
                EncoderParameters encoderParams = new EncoderParameters();
                long[] quality = new long[1];
                quality[0] = 100;
                EncoderParameter encoderParam = new EncoderParameter(Encoder.Quality, quality);
                encoderParams.Param[0] = encoderParam;
                //获得包含有关内置图像编码解码器的信息的 ImageCodecInfo 对象。
                ImageCodecInfo[] arrayICI = ImageCodecInfo.GetImageEncoders();
                ImageCodecInfo jpegICI = null;

                foreach (ImageCodecInfo imageCodecInfo in arrayICI)
                {
                    if (imageCodecInfo.FormatDescription.Equals("JPEG"))
                    {
                        jpegICI = imageCodecInfo;
                        //设置 JPEG 编码
                        break;
                    }
                }

                if (jpegICI != null)
                {
                    image.Save(savePath, jpegICI, encoderParams);
                }
                else
                {
                    image.Save(savePath, imageFormat);
                }
            }
            else
            {
                //保存图片。
                image.Save(savePath, imageFormat);
            }
        }

        /// <summary>
        /// 获得文件名并去掉后缀。
        /// </summary>
        /// <param name="path">文件完所路径。</param>
        /// <returns>不带路径与后缀的文件名。</returns>
        private string GetFileName(string path)
        {
            string fileName = Path.GetFileName(path);
            return fileName.Substring(0, fileName.Length - Path.GetExtension(fileName).Length);
        }
    }
}